package com.laizhenghua.example.utils;

import com.laizhenghua.example.configuration.ColumnConfig;
import com.laizhenghua.example.configuration.ExcelConfig;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @description: xml解析器封装
 * @author: laizhenghua
 * @date: 2022/4/25 21:19
 */
public class XmlParser {
    private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(XmlParser.class);
    public static final String DEFAULT_PATH = "config";
    /**
     * 文件最后更新时间缓存
     */
    private static Map<String, Long> configTimeCache = new ConcurrentHashMap<String, Long>();
    /**
     * 配置文件缓存
     */
    private static Map<String, Map<String, ExcelConfig>> configCache = new ConcurrentHashMap<>();

    private static ExcelConfig getConfigFromCache(String fileName, String configName) {
        if (configCache.containsKey(fileName)) {
            Map<String, ExcelConfig> cache = configCache.get(fileName);
            if (cache.containsKey(configName)) {
                return cache.get(configName);
            }
        }
        return null;
    }
    /**
     * 根据指定的路径、文件名、配置名获取配置信息
     * @param path 类路径下开辟的文件目录名(一般资源文件都放在resources目录下)
     * @param fileName xml文件
     * @param configName 配置项名
     */
    public static ExcelConfig getConfigInfo(String path, String fileName, String configName) {
        String filePath = "";
        if (path != null && path.length() > 0) {
            filePath += path;
        } else {
            filePath += DEFAULT_PATH;
        }
        if (fileName != null && fileName.length() > 0) {
            filePath += "/" + fileName;
        }
        filePath = filePath.replaceAll("\\\\", "/");
        filePath = filePath.replaceAll("//", "/");

        if (filePath.startsWith("/")) {
            filePath = filePath.substring(1);
        }
        ExcelConfig result = null;
        try {
            URL url = XmlParser.class.getClassLoader().getResource(filePath);
            if (url == null) {
                 log.error(String.format("not found ExcelConfig [path=%s], [fileName=%s], [configName=%s]", path, fileName, configName));
                return null;
            }
            // 1.获取文件最后更新时间
            String fullPath = new File(url.toURI()).getAbsolutePath();
            Long lastModified = getLastModifiedTime(fullPath);
            // 2.尝试从缓存中获取配置信息(最后更改时间与缓存相等)
            Long cacheModified = configTimeCache.get(filePath); // filePath=config/xxx.xml
            if (lastModified.equals(cacheModified)) {
                return getConfigFromCache(fileName, configName);
            }
            // 3.缓存中不存在或文件已更新过
            Map<String, ExcelConfig> excelConfigMap = resolveConfigInfo(fullPath, filePath);
            if (excelConfigMap == null) {
                log.error(String.format("not found ExcelConfig [path=%s], [fileName=%s], [configName=%s]", path, fileName, configName));
                return null;
            }
            if (excelConfigMap.containsKey(configName)) {
                result = excelConfigMap.get(configName);
            }
            // 4.更新缓存
            updateCache(lastModified, filePath, fileName, excelConfigMap);
        } catch (Exception e) {
            log.error(e);
        }
        return result;
    }

    /**
     * 获取xml文件最后更新时间戳
     * @param fullPath
     * @return
     */
    private static Long getLastModifiedTime(String fullPath) {
        Long max = null;
        if (fullPath == null || fullPath.length() == 0) {
            return max;
        }
        File file = new File(fullPath);
        if (file.exists()) {
            max = file.lastModified();
        }
        return max;
    }

    /**
     * 解析xml配置文件
     * @param fullPath
     * @param filePath
     */
    private static Map<String, ExcelConfig> resolveConfigInfo(String fullPath, String filePath) {
        if (fullPath == null) {
            return null;
        }
        File file = new File(fullPath);
        if (file.exists()) {
            try {
                SAXReader reader = new SAXReader(); // 创建SAXReader对象，用于读取xml文件
                Element root = reader.read(file).getRootElement(); // 通过 read() 解析为Document对象 在获取root根元素节点
                return resolveRoot(root, filePath);
            } catch (Exception e) {
                log.error(e);
            }
        }
        return null;
    }

    /**
     * 解析根节点含有的元素(转为ExcelConfig配置对象)
     * @param root
     * @param filePath
     */
    private static Map<String, ExcelConfig> resolveRoot(Element root, String filePath) {
        Map<String, ExcelConfig> result = new HashMap<>();
        // Iterator<Element> elementIterator = root.elementIterator();
        List<Node> nodeList = root.selectNodes("//class");
        Iterator<Node> iterator = nodeList.iterator();
        while (iterator.hasNext()) {
            Element element = (Element) iterator.next();
            ExcelConfig config = new ExcelConfig();
            config.setFilePath(filePath);
            config.setName(element.attributeValue("name"));
            config.setDbName(element.attributeValue("dbname"));
            config.setPackagePath(element.attributeValue("package"));
            config.setColumnConfigList(resolveSubNode(element));
            result.put(config.getName(), config);
        }
        return result;
    }
    private static List<ColumnConfig> resolveSubNode(Element element) {
        // List<Node> nodeList = element.selectNodes("//property");
        Iterator<Element> iterator = element.elementIterator();
        List<ColumnConfig> configList = new ArrayList<>();
        while (iterator.hasNext()) {
            Element node = (Element) iterator.next();
            ColumnConfig config = new ColumnConfig();
            config.setName(node.attributeValue("name"));
            config.setType(node.attributeValue("type"));
            Iterator<Element> elementIterator = node.elementIterator();
            while (elementIterator.hasNext()) {
                Element el = elementIterator.next();
                config.setColumnName(el.attributeValue("name"));
                String notnull = el.attributeValue("notnull");
                if (notnull != null) {
                    config.setNotNull(Boolean.parseBoolean(notnull));
                }
                config.setTitle(el.getStringValue());
            }
            configList.add(config);
        }
        return configList;
    }

    private static void updateCache(Long lastModified, String filePath, String fileName, Map<String, ExcelConfig> configMap) {
        configTimeCache.put(filePath, lastModified);
        configCache.put(fileName, configMap);
    }
}
